#ifndef _CANARY2_HTTP_CHUNKED_BUFFER_H_
#define _CANARY2_HTTP_CHUNKED_BUFFER_H_

#include <cstddef>
#include <vector>
#include <iterator>
#include <span>
#include <compare>
#include "../util/fill_zero.h"

namespace wilson::http
{
    class chunked_buffer;
    class chunked_buffer_as_integral;
    class chunked_buffer_as_integral_iterator
    {
    public:
        using value_type        = ::std::byte;
        using iterator_category = ::std::random_access_iterator_tag;
        using difference_type   = ssize_t;
        using pointer           = const value_type*;
        using reference         = const value_type&;
        using underlying_iter   = typename ::std::span<const value_type>::iterator;

    public:
        chunked_buffer_as_integral_iterator(
            const chunked_buffer_as_integral& parent, 
            size_t curblk, 
            size_t curbyte
        ) noexcept;

        chunked_buffer_as_integral_iterator& operator++() noexcept;
        chunked_buffer_as_integral_iterator  operator++(int) noexcept;
        chunked_buffer_as_integral_iterator& operator+=(difference_type) noexcept;
        chunked_buffer_as_integral_iterator  operator+(difference_type) const noexcept;
        chunked_buffer_as_integral_iterator& operator--() noexcept;
        chunked_buffer_as_integral_iterator  operator--(int) noexcept;
        chunked_buffer_as_integral_iterator& operator-=(difference_type) noexcept;
        chunked_buffer_as_integral_iterator  operator-(difference_type) const noexcept;

        reference operator*() const noexcept;

        friend bool
        operator==(const chunked_buffer_as_integral_iterator& lhs, 
                   const chunked_buffer_as_integral_iterator& rhs) noexcept;

        friend ::std::strong_ordering 
        operator<=>(const chunked_buffer_as_integral_iterator& lhs, 
                    const chunked_buffer_as_integral_iterator& rhs) noexcept;

        friend difference_type 
        operator-(const chunked_buffer_as_integral_iterator& lhs, 
                  const chunked_buffer_as_integral_iterator& rhs) noexcept;

        ::std::span<const ::std::byte> current_block() const noexcept;

    private:
        size_t m_cur_blk{};
        size_t m_cur_byte{};
        const chunked_buffer_as_integral* m_parent{ nullptr };
    };

    class chunked_buffer_as_integral
    {
    public:
        chunked_buffer_as_integral(chunked_buffer& cb) noexcept
            : m_cb{ cb }
        {
        }

        chunked_buffer_as_integral_iterator 
        begin() const noexcept;

        chunked_buffer_as_integral_iterator 
        end() const noexcept;

        chunked_buffer_as_integral_iterator 
        cbegin() const noexcept { return begin(); }

        chunked_buffer_as_integral_iterator 
        cend() const noexcept { return end(); }

        chunked_buffer& m_cb;
    };

    class chunked_buffer
    {
    public:
        chunked_buffer(size_t default_blk_size = 4096U) 
            : m_default_blk_size{ default_blk_size }{}
        ~chunked_buffer();
        chunked_buffer(chunked_buffer&& other) noexcept
            : m_blks{ ::std::move(other.m_blks) }, 
              m_default_blk_size{ other.m_default_blk_size }
        {
            util::fill_zero(other.m_blks);
        }

        ::std::span<::std::byte> next_write_block()
        {
            return next_write_block(m_default_blk_size);
        }
        ::std::span<::std::byte> next_write_block(size_t nbytes);
        ::std::span<const ::std::byte> read_block(size_t idx) const;
        ::std::span<::std::byte> get_writeable_block(size_t idx);

        size_t blocks_size() const noexcept { return m_blks.size(); }
        size_t size() const noexcept;

        chunked_buffer_as_integral 
        as_integral() noexcept { return { *this }; }

        auto begin() noexcept(noexcept(m_blks.begin())) 
        { return m_blks.begin(); }

        auto end() noexcept(noexcept(m_blks.end())) 
        { return m_blks.end(); }

        auto cbegin() const noexcept(noexcept(m_blks.cbegin())) 
        { return m_blks.begin(); }

        auto cend() const noexcept(noexcept(m_blks.cend())) 
        { return m_blks.end(); }

        void merge_to_unique();
        
    private:
        ::std::vector<::std::span<::std::byte>> m_blks;
        const size_t m_default_blk_size;
    };

}

#endif
